//: C20:BankTeller.cpp
// Using a queue and simulated multithreading
// To model a bank teller system

#include <iostream>
#include <iterator>
#include <queue>
#include <list>
#include <cstdlib>
#include <ctime>

using namespace std;

class Customer {
public:
    Customer() : serviceTime(0) {}
    
    Customer(int tm) : serviceTime(tm) {}

    int getTime() {
        return serviceTime;
    }

    void setTime(int time) {
        serviceTime = time;
    }

    friend ostream& operator<<(ostream& os, const Customer& c) {
        return os << '[' << c.serviceTime << ']';
    }
private:
    int serviceTime;
};

class Teller {
public:
    Teller(queue<Customer>& cq) : customers(cq), 
        ttime(0), busy(false) {}

    // Compiler can't synthesize operator=
    Teller& operator=(const Teller& rv) {
        customers = rv.customers;
        current = rv.current;
        ttime = rv.ttime;
        busy = rv.busy;

        return *this;
    }

    bool isBusy() {
        return busy;
    }

    void run(bool recursion = false) {
        if (!recursion) {
            ttime = slice;
        }

        int servtime = current.getTime();

        if (servtime > ttime) {
            servtime -= ttime;
            current.setTime(servtime);
            busy = true; // Still working on current
            return;
        }

        if (servtime < ttime) {
            ttime -= servtime;

            if (!customers.empty()) {
                current = customers.front();
                customers.pop(); // Remove it
                busy = true;
                run(true); // Recurse
            }

            return;
        }

        if (servtime == ttime) {
            // Done with current, set to empty:
            current = Customer(0);
            busy = false;
            return; // No more time in this slice
        }
    }
private:
    queue<Customer>& customers;
    Customer current;
    static const int slice = 5;
    int ttime; // Time left in slice
    bool busy; // Is teller serving a customer?
};

// Inherit to access protected implementation:
class CustomerQ : public queue<Customer> {
public:
    friend ostream& operator<<(ostream& os, const CustomerQ& cd) {
        copy(cd.c.begin(), cd.c.end(), ostream_iterator<Customer>(os, ""));
        return os;
    }
};

int main(int argc, char* argv[]) {
    CustomerQ customers;
    list<Teller> tellers;
    typedef list<Teller>::iterator TellIt;
    tellers.push_back(Teller(customers));
    srand(time(0)); // Seed random number generator

    while (true) {
        // Add a random number of customers to the
        // queue, with random service times:
        for (int i = 0; i < rand() % 5; i++) {
            customers.push(Customer(rand() % 15 + 1));
        }

        cout << '{' << tellers.size() << '}' << customers << endl;

        // Have the tellers service the queue:
        for (TellIt i = tellers.begin(); i != tellers.end(); i++) {
            (*i).run();
        }
        cout << '{' << tellers.size() << '}' << customers << endl;

        // If customers line is too long, add another teller:
        if (customers.size() / tellers.size() > 2) {
            tellers.push_back(Teller(customers));
        }

        // If tellers line is short enough, remove a teller:
        if (tellers.size() > 1 && customers.size() / tellers.size() < 2) {
            for (TellIt i = tellers.begin(); i != tellers.end(); i++) {
                if (!(*i).isBusy()) {
                    tellers.erase(i);
                    break; // Out of for loop
                }
            }
        }
    }

    return 0;
} ///:~
